Scalaのロギングライブラリwoofを試してみた
はじめに
12/16にLEGO Groupの初めてのOSS ライブラリとしてリリースされたScalaのロギングライブラリwoofを眺めてみました。このタイミングでリリースするのすごいな。
We are very proud to announce the LEGO Group's first OSS library release — Woof https://t.co/JPrzx7JRoZ, a small Scala 3 logging library ?https://t.co/99KhEgrfUS
Do we have any #Scala users out there? ?— LEGO Engineering (@LEGOEngineering) December 16, 2021
woofの特徴
リポジトリでも紹介されていますが、以下が特徴です。
- Scala 3のライブラリ(コードが完全に3系で記述されている)
- Cats Effectを使っている
- マクロを使って呼び出し側の情報を出力する(リフレクションなし)
- Scalaのコードで設定が可能
使ってみる
基本的な使い方はREADMEに例があるのでそちらを参照してもらうとしてREADMEでさらっと流されているいくつかのAPIを調べてみます。
Output
例で最初に定義されているのはOutputです。ログの実際の出力はOutputに委譲されるようです。
trait Output[F[_]]: def output(str: String): F[Unit] def outputError(str: String): F[Unit] end Output object Output: def fromConsole[F[_]: Console]: Output[F] = new Output[F]: def output(str: String): F[Unit] = Console[F].println(str) def outputError(str: String): F[Unit] = Console[F].errorln(str) end Output
コンパニオンオブジェクトにファクトリがあるので試してみます。
//Output by cats effect Console[F] val outputFromConsole = Output.fromConsole[IO] def run: cats.effect.IO[Unit] = for given Logger[IO] <- Logger.makeIoLogger(outputFromConsole) _ <- program.withLogContext("trace-id", "4d334544-6462-43fa-b0b1-12846f871573") _ <- Logger[IO].info("Now the context is gone") yield ()
これはErrorレベル以上で標準エラー出力が使われます。
Filter
Filterはログイベント(LogLine)を出力するかどうか決定する述語で、レベルやクラス名でフィルタリングするビルダーがプリセットされています。
type Filter = LogLine => Boolean object Filter: val atLeastLevel: LogLevel => Filter = level => line => line.level >= level val exactLevel: LogLevel => Filter = level => line => line.level == level val regexFilter: Regex => Filter = regex => line => regex.matches(line.info.enclosingClass) val nothing: Filter = _ => false val everything: Filter = _ => true given Monoid[Filter] with def empty: Filter = nothing def combine(f: Filter, g: Filter): Filter = f or g end Filter extension (f: Filter) infix def and(g: Filter): Filter = line => f(line) && g(line) infix def or(g: Filter): Filter = line => f(line) || g(line)
フィルターの合成を試してみます。
given Filter = Filter.exactLevel(LogLevel.Info) or Filter.exactLevel(LogLevel.Error) // 上記と同じ; Monoidを使う場合 import org.legogroup.woof.Filter.given given Filter = Filter.exactLevel(LogLevel.Info) |+| Filter.exactLevel(LogLevel.Error)
2021-12-20 22:19:12 [INFO ] trace-id=4d334544-6462-43fa-b0b1-12846f871573 Main$: HEY! (Main.scala:26) 2021-12-20 22:19:12 [INFO ] Main$: Now the context is gone (Main.scala:20) 2021-12-20 22:19:12 [ERROR] trace-id=4d334544-6462-43fa-b0b1-12846f871573 Main$: I give up (Main.scala:28)
Printer
ログのフォーマットを決めるのがPrinterです。
trait Printer: def toPrint( epochMillis: EpochMillis, level: LogLevel, info: LogInfo, message: String, context: List[(String, String)], ): String end Printer
例で使われているNoColorPrinterの他にColorPrinterがあります。
given Filter = Filter.everything given Printer = ColorPrinter()
まとめ
woofをざっと眺めてみました。Scala 3で簡潔に書かれていて拡張も簡単にできそうな印象を受けました。これからもpure Scala 3なライブラリが出てくると思うので楽しみです。